package chat

import (
	"cat_mouse/core/settings"
	"cat_mouse/infra/common"
	"cat_mouse/infra/utils/log"
	"cat_mouse/infra/utils/redis"
	"encoding/json"
	"fmt"
	"github.com/google/uuid"
	"github.com/kirinlabs/HttpRequest"
	"github.com/pkg/errors"
	"net/http"
	"strconv"
	"sync"
)

type OpenImUser struct {
	UserId   string `json:"userID"`
	Nickname string `json:"nickname"`
	FaceUrl  string `json:"faceURL"`
}

type BaseRespDto struct {
	ErrCode int
	ErrMsg  string
	ErrDlt  string
}

type AdminTokenRespDto struct {
	BaseRespDto
	Data struct {
		Token             string
		ExpireTimeSeconds int
	}
}

type UserRespDto struct {
	UserId           string `json:"userID"`
	Nickname         string `json:"nickname"`
	FaceUrl          string `json:"faceURL"`
	Ex               string `json:"ex"`
	CreateTime       int    `json:"createTime"`
	AppMangerLevel   int    `json:"appMangerLevel"`
	GlobalRecvMsgOpt int    `json:"globalRecvMsgOpt"`
}

type UsersRespDto struct {
	BaseRespDto
	Data struct {
		Total int
		Users []UserRespDto
	}
}

// OpenImService https://doc.rentsoft.cn/restapi/userManagement/userRegister
type OpenImService struct {
}

func (o *OpenImService) getAuthHeaders() map[string]string {
	return map[string]string{
		"operationID": strconv.Itoa(int(uuid.New().ID())),
		"token":       o.GetCacheUserToken(settings.Config.OpenIm.AdminUserId),
	}
}

func (o *OpenImService) closeResp(resp *HttpRequest.Response) {
	if resp != nil {
		_ = resp.Close()
	}
}

func (o *OpenImService) CreateUser(users []OpenImUser) error {
	var (
		req     *HttpRequest.Request
		resp    *HttpRequest.Response
		respDto BaseRespDto
		err     error
	)
	url := fmt.Sprintf("%s/user/user_register", settings.Config.OpenIm.Host)
	params := map[string]interface{}{
		"secret": settings.Config.OpenIm.Secret,
		"users":  users,
	}
	headers := map[string]string{"operationID": strconv.Itoa(int(uuid.New().ID()))}
	req = HttpRequest.NewRequest().SetTimeout(3).SetHeaders(headers)
	if resp, err = req.JSON().Post(url, params); err != nil {
		return errors.Wrap(err, "OpenIM Create User Error")
	}
	defer o.closeResp(resp)
	if resp.StatusCode() != http.StatusOK {
		log.Logger.Error(resp.StatusCode(), err)
		return errors.Wrap(err, "OpenIM Create User Error")
	}
	bs, _ := resp.Body()
	if err = json.Unmarshal(bs, &respDto); err != nil {
		return errors.Wrap(err, "OpenIM Create User Error")
	}
	if respDto.ErrCode != 0 {
		log.Logger.Error(respDto.ErrDlt)
		return errors.New(respDto.ErrMsg)
	}
	return nil
}

func (o *OpenImService) UpdateUser(user OpenImUser) error {
	var (
		req     *HttpRequest.Request
		resp    *HttpRequest.Response
		respDto BaseRespDto
		err     error
	)
	url := fmt.Sprintf("%s/user/update_user_info", settings.Config.OpenIm.Host)
	params := map[string]interface{}{
		"userInfo": user,
	}
	req = HttpRequest.NewRequest().SetTimeout(3).SetHeaders(o.getAuthHeaders())
	if resp, err = req.JSON().Post(url, params); err != nil {
		return errors.Wrap(err, "OpenIM UpdateUser Error")
	}
	defer o.closeResp(resp)
	if resp.StatusCode() != http.StatusOK {
		log.Logger.Error(resp.StatusCode(), err)
		return errors.Wrap(err, "OpenIM UpdateUser Error")
	}
	bs, _ := resp.Body()
	if err = json.Unmarshal(bs, &respDto); err != nil {
		return errors.Wrap(err, "OpenIM UpdateUser Error")
	}
	if respDto.ErrCode != 0 {
		log.Logger.Error(respDto.ErrDlt)
		return errors.New(respDto.ErrMsg)
	}
	return nil
}

func (o *OpenImService) ListUser(page, size int) (UsersRespDto, error) {
	var (
		req     *HttpRequest.Request
		resp    *HttpRequest.Response
		respDto UsersRespDto
		err     error
	)
	url := fmt.Sprintf("%s/user/get_users", settings.Config.OpenIm.Host)
	params := fmt.Sprintf(`{"pagination": {"pageNumber": %d,"showNumber": %d}}`, page, size)
	req = HttpRequest.NewRequest().SetTimeout(3).SetHeaders(o.getAuthHeaders())
	if resp, err = req.JSON().Post(url, params); err != nil {
		return respDto, errors.Wrap(err, "OpenIM ListUser Error")
	}
	defer o.closeResp(resp)
	if resp.StatusCode() != http.StatusOK {
		log.Logger.Error(resp.StatusCode(), err)
		return respDto, errors.Wrap(err, "OpenIM ListUser Error")
	}
	bs, _ := resp.Body()
	if err = json.Unmarshal(bs, &respDto); err != nil {
		return respDto, errors.Wrap(err, "OpenIM ListUser Error")
	}
	if respDto.ErrCode != 0 {
		log.Logger.Error(respDto.ErrDlt)
		return respDto, errors.New(respDto.ErrMsg)
	}
	return respDto, nil
}

func (o *OpenImService) SearchUser(userId string, page, size int) (UsersRespDto, error) {
	var (
		req     *HttpRequest.Request
		resp    *HttpRequest.Response
		respDto UsersRespDto
		err     error
	)
	url := fmt.Sprintf("%s/friend/search", settings.Config.OpenIm.Host)
	params := fmt.Sprintf(`{
		"keyword": "%s",
		"pagination": {
			"pageNumber": %d,
			"showNumber": %d}
		}`, userId, page, size)
	req = HttpRequest.NewRequest().SetTimeout(3).SetHeaders(o.getAuthHeaders())
	if resp, err = req.JSON().Post(url, params); err != nil {
		return respDto, errors.Wrap(err, "OpenIM SearchUser Error")
	}
	defer o.closeResp(resp)
	if resp.StatusCode() != http.StatusOK {
		log.Logger.Error(resp.StatusCode(), err)
		return respDto, errors.Wrap(err, "OpenIM SearchUser Error")
	}
	bs, _ := resp.Body()
	if err = json.Unmarshal(bs, &respDto); err != nil {
		return respDto, errors.Wrap(err, "OpenIM ListUser Error")
	}
	if respDto.ErrCode != 0 {
		log.Logger.Error(respDto.ErrDlt)
		return respDto, errors.New(respDto.ErrMsg)
	}
	return respDto, nil
}

func (o *OpenImService) SendMsg(sendId, recvId, content string) (UsersRespDto, error) {
	var (
		req     *HttpRequest.Request
		resp    *HttpRequest.Response
		respDto UsersRespDto
		err     error
	)
	url := fmt.Sprintf("%s/msg/send_msg", settings.Config.OpenIm.Host)
	//params := fmt.Sprintf(`{"sendID": "%s","recvID": "%s","content": "%s","contentType": 101,"sessionType":1}`, sendId, recvId, content)
	params := map[string]interface{}{
		"sessionType": 1,
		"sendID":      sendId,
		"recvID":      recvId,
		"contentType": 101,
		"content": map[string]interface{}{
			"content": content,
		},
	}
	req = HttpRequest.NewRequest().SetTimeout(3).SetHeaders(o.getAuthHeaders())
	if resp, err = req.JSON().Post(url, params); err != nil {
		return respDto, errors.Wrap(err, "OpenIM SendMsg Error")
	}
	defer o.closeResp(resp)
	if resp.StatusCode() != http.StatusOK {
		b, _ := resp.Body()
		log.Logger.Error(resp.StatusCode(), err, string(b))
		return respDto, errors.Wrap(err, "OpenIM SendMsg Error")
	}
	bs, _ := resp.Body()
	if err = json.Unmarshal(bs, &respDto); err != nil {
		return respDto, errors.Wrap(err, "OpenIM ListUser Error")
	}
	if respDto.ErrCode != 0 {
		log.Logger.Error(respDto.ErrDlt)
		return respDto, errors.New(respDto.ErrMsg)
	}
	return respDto, nil
}

func (o *OpenImService) GetCacheUserToken(userId string) string {
	var (
		tokenStr string
		token    string
		expired  int
		err      error
	)
	cacheKey := fmt.Sprintf(common.OpenImUserTokenCacheKey, userId)
	if tokenStr, err = redis.Get(cacheKey); err == nil && tokenStr != "" {
		if err = json.Unmarshal([]byte(tokenStr), &token); err == nil {
			return token
		}
	}
	if token, expired, err = o.GetUserToken(userId); err != nil {
		log.Logger.Error(err)
		return ""
	}
	_ = redis.Set(cacheKey, token, expired-60)
	return token
}

func (o *OpenImService) GetUserToken(userId string) (string, int, error) {
	var (
		req     *HttpRequest.Request
		resp    *HttpRequest.Response
		respDto AdminTokenRespDto
		err     error
	)
	url := fmt.Sprintf("%s/auth/user_token", settings.Config.OpenIm.Host)
	params := map[string]interface{}{
		"secret":     settings.Config.OpenIm.Secret,
		"platformID": 1,
		"userID":     userId,
	}
	headers := map[string]string{
		"operationID": strconv.Itoa(int(uuid.New().ID())),
	}
	req = HttpRequest.NewRequest().SetTimeout(3).SetHeaders(headers)
	if resp, err = req.JSON().Post(url, params); err != nil {
		return "", 0, err
	}
	defer o.closeResp(resp)
	if resp.StatusCode() != http.StatusOK {
		log.Logger.Error(resp.StatusCode(), err)
		return "", 0, errors.Wrap(err, "OpenIM GetUserToken Error")
	}
	bs, _ := resp.Body()
	if err = json.Unmarshal(bs, &respDto); err != nil {
		return "", 0, err
	}
	if respDto.ErrCode != 0 {
		log.Logger.Error(respDto.ErrDlt)
		return "", 0, errors.New(respDto.ErrMsg)
	}
	return respDto.Data.Token, respDto.Data.ExpireTimeSeconds, nil
}

var (
	openImSvc     *OpenImService
	openImSvcOnce sync.Once
)

func NewOpenImService() *OpenImService {
	openImSvcOnce.Do(func() {
		openImSvc = &OpenImService{}
	})
	return openImSvc
}
